package com.cloud.common.support;

import com.cloud.common.context.AppContext;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁
 *
 * @author fengwenjin
 */
public class RemoteLock {

    private static final ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors());
    private static final long TIMEOUT_MILLIS = 6000L;

    private static RedisTemplate<String, Object> getRedisTemplate() {
        return (RedisTemplate<String, Object>) AppContext.getBean(RedisTemplate.class);
    }

    /**
     * 加锁
     *
     * @param key
     * @param waitMillis
     * @return boolean
     */
    public static boolean lock(String key, long waitMillis) {
        long beginTime = System.currentTimeMillis();
        while (true) {
            String value = UUID.randomUUID().toString();
            if (getRedisTemplate().opsForValue().setIfAbsent(key, value, TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
                leaseLockTime(key, value);
                return true;
            }
            if (System.currentTimeMillis() - beginTime > waitMillis) {
                break;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
        return false;
    }

    /**
     * 解锁
     *
     * @param key
     */
    public static void unlock(String key) {
        getRedisTemplate().delete(key);
    }

    /**
     * 延续锁的时间
     *
     * @param key
     * @param value
     */
    private static void leaseLockTime(String key, String value) {
        scheduledExecutorService.schedule(() -> {
            RedisTemplate<String, Object> redisTemplate = getRedisTemplate();
            String cacheValue = (String) redisTemplate.opsForValue().get(key);
            if (cacheValue != null && cacheValue.equals(value)) {
                redisTemplate.expire(key, TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
                leaseLockTime(key, value);
            }
        }, TIMEOUT_MILLIS / 3, TimeUnit.MILLISECONDS);
    }
}
